﻿/* Author: Tomer Paz
   games.com
   2007
*/
// the game Commands concrete class
// implements the network commands Sent and Recieved to/from game server
// encapsulates (composite) a Socket object to connect to server.
// A Singleton.

package monopoly.CMD {

	import monopoly.CMD.ICMD;
	import monopoly.CMD.CMDResponse;
	import monopoly.CMD.CMDResponseEvent;
	import monopoly.IFS.Net.SocketW;
	import monopoly.IFS.GLOBALS;
	import monopoly.IFS.utils.encryption.*;
	import flash.errors.IOError;
	import flash.errors.EOFError;
	import flash.events.IOErrorEvent;
	import flash.utils.ByteArray;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.events.EventDispatcher;
	import flash.utils.Endian;
	import flash.external.ExternalInterface;

	
	public class CMD extends EventDispatcher implements ICMD
		{
		/* the state constants to describe the protocol command status */
		
		public static const SRVHLO:uint		= 100;		// SERVER sends handshake data-Challange after connection estab, a.k.a. SRVHLO
		public static const INTRO:uint		= 120;		// Client sends Response for hanshake info (hashed client ID+server key) a.k.a. INTRO
		public static const ACCEPT:uint		= 130;		// Server sends final "Authorized" acknowledge or otherwise disconnects! a.k.a "ACCEPT"
		public static const RESIGN:uint		= 998;		//     [C]   998 - RESIGN - user does not disconnect but resign from being Active player
		public static const DISCONNECT:uint = 999;		// Server push operation
	
		public static const STATUS:uint 	= 180;		// Server push/respond to Player_Type operation or any other operation or game state update
		
		/* server STATUS options */

		public static const STATUS_PLAY:uint  = 181;		// tell client to do its turn
		public static const STATUS_UPDATE:uint= 182;		// update client with board / players status (properties, location, resigned etc')
		public static const STATUS_WAIT:uint  = 183;
		public static const STATUS_DIES:uint  = 184;		// server retunred Dies results
			
		/* add chat systen*/
		
		public static const CHAT:uint 		  = 150;		// server has Chat message coming in or -
															// client send chat message to server
		public static const BROADCAST:uint	  = 155;		// server broadcast to log
			public static const BCAST_TYPE_JOIN:uint   = 0;
			public static const BCAST_TYPE_START:uint  = 1;
			public static const BCAST_TYPE_DISCON:uint = 2;
			public static const BCAST_TYPE_QUIT:uint   = 3;
			
		// client init game
		public static const PLAYER_TYPE:uint 	= 160;		// Client Join-room command operation
		public static const JOIN_RESPONSE:uint	= 165;		// Server response to Join-room. Lists all joined players
		public static const PLAYER_START:uint	= 170;		// Client Start-game (relevant for active players only)
		
		/* Client game protocol commands */

		public static const THROW_DIES:uint 	= 400;		// Client command operation
		public static const DONE_MOVE:uint  	= 420;		// Client command operation (and server response)
		public static const ACTION_NONE:uint	= 430;		// Client...
		public static const ACTION_PAY:uint 	= 440;		// Client...
		public static const ACTION_BUY:uint 	= 450;		// Client...
		public static const ACTION_EXCHANGE:uint= 451;
		public static const ACTION_PARTNER:uint	= 452;
		public static const ACTION_SELL:uint	= 460;		// Client...
		public static const ACTION_MORTGAGE:uint= 470;		// Client...
		public static const ACTION_UPGRADE:uint	= 480;
		public static const ACTION_AUCTION:uint = 490;
		
		public static const ACTION_ENDTURN:uint = 555;		// Client...
		
		public static const ACTION_BUY_RESP:uint= 600;		// in response to Other player (BUY)
		public static const ACTION_EXC_RESP:uint= 610;		// in response to Other player (EXCHANGE)
		public static const ACTION_PAR_RESP:uint= 620;		// in response to Other player (PARTNER)
		public static const ACTION_SEL_RESP:uint= 630;		// in response to Other player (SELL)
		public static const ACTION_AUC_RESP:uint= 690;		// in response to Other player (AUCTION)
		public static const ACTION_AUC_REQ:uint = 691;		// server round-rubin response to one of other player auction request
		
		/* server response to client Action */
		
		public static const ACTION_RES:uint		= (DONE_MOVE || ACTION_BUY || ACTION_EXCHANGE || ACTION_PARTNER || ACTION_SELL || ACTION_MORTGAGE || ACTION_UPGRADE || ACTION_AUCTION);
		public static const	ACTION_REQ:uint		= (ACTION_BUY_RESP || ACTION_EXC_RESP || ACTION_PAR_RESP || ACTION_SEL_RESP || ACTION_AUC_RESP || ACTION_AUC_REQ);
		
		public static const ACTION_RES_DEAL:uint= 0;
		public static const ACTION_RES_REJ:uint	= 1;		// more will be added as we go... currently only "REJECT"
	
		/* some constant sizes */
		
		private static const ENDIAN_TYPE:String		= Endian.BIG_ENDIAN;	// for Unix and Java Servers network byte order
//		private static const ENDIAN_TYPE:String		= Endian.LITTLE_ENDIAN;	// for Windows & Intel
		
		
		// a singleton
		private static var _instance:CMD;
		
		/* connection variables/objects to work with */
		
		private var _ServerSocket:SocketW;
		private var _ClientID:int;
		private var _SharedSecret:String = null;
		private var _Authorized:Boolean = false;
		private var _myIP:String;

		/* server details */
		
		private var _SessionID:String;
		private var _ServerName:uint;
		private var _ServerVersion:uint;
		
		// Maps a command to a processing function
	    private var _commandMap:Object;
    
    	// Keeps track of the current protocol state
    	private var _currentCMD:int = 0;
    

		public function CMD(parameter:SingletonEnforcer):void
			{
			}
			
		// Singleton implementation	
		public static function getInstance(HostName:String, Port:int, myIP:String, secret:String):CMD
			{
	        if(_instance == null) 
				{
	            _instance = new CMD(new SingletonEnforcer());
				//_instance._CMDByteArray	= new ByteArray;
				
				/* Initialzes the Server Triggering Client Action map */
				
				_instance._commandMap = new Object(  );
				_instance._commandMap[ SRVHLO ]		    = _instance.readSRVHLO;
				_instance._commandMap[ INTRO ]		    = _instance.sendINTRO;
				_instance._commandMap[ ACCEPT ]		    = _instance.readACCEPT;
				_instance._commandMap[ JOIN_RESPONSE ]  = _instance.readJOINRESP;
				_instance._commandMap[ STATUS ]		    = _instance.readSTATUS;		// to be updated later - moved to controller
				_instance._commandMap[ BROADCAST ]	    = _instance.readBROADCAST;
				_instance._commandMap[ CHAT ]		    = _instance.readCHAT;
				_instance._commandMap[ ACTION_RES ]	    = _instance.readACTIONRES;
				_instance._commandMap[ ACTION_REQ ]	    = _instance.readACTIONREQ;
				_instance._commandMap[ DISCONNECT ]     = _instance.CMDdisconnect;
      
		      	/* set current state */
				
				_instance._currentCMD = SRVHLO;
				
				/* Create and connect the socket wrapper */
				
				_instance._ServerSocket = new SocketW(ENDIAN_TYPE);
      
   				_instance._ServerSocket.addEventListener( ProgressEvent.SOCKET_DATA, _instance.onSocketData );
				// Add an event listener to be notified when the server disconnects
				// the client
				_instance._ServerSocket.addEventListener( Event.CLOSE, _instance.onDisconnect );

				/* register listener for socket error events */
			
				_instance._ServerSocket.addEventListener( IOErrorEvent.IO_ERROR, _instance.onConnectError);

				_instance._myIP = myIP;
				_instance._SharedSecret = secret;
				// connect
				_instance.ConnectServer(HostName, Port);
	         	}
	        return CMD._instance;
	      	}
		
		public function set ClientID(clientid:uint):void
			{
			_instance._ClientID = clientid;
			}
		public function set SessionID(sessionID:String):void
			{
			_instance._SessionID = sessionID;
			}

				
		/* connect server */
		
		private function ConnectServer(HostName:String, Port:int):void
			{
			var Connected:Boolean = false;
			var retries:int = 0;
			
			while ((Connected == false) && (retries < 2))
				{
				try {
					retries += 1;
					_instance._ServerSocket.connect( HostName, Port );
					Connected = true;
					}
				catch(error:IOError)
					{
					trace(error);
					retries += 1;
					}
				catch(error:SecurityError)
					{
					trace (error);
					retries += 1;
					}
				}
			}
		private function onConnectError( event:IOErrorEvent ):void
			{
			if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
				trace ("[CMD][onConnectError] error connecting to server. " + "error: " + event);
			// TODO: retry connect
			}
			
		/* state machine */
		
		public function get State():int { return _instance._currentCMD; }
		public function set State(state:int):void 
			{ 
			_instance._currentCMD = state; 
			if (state != STATUS)
				dispatchEvent(new Event(Event.CHANGE));
			}

		public function get Authorized():Boolean { return _instance._Authorized; }

		/* handle incoming Data from Socket */
				
		private function onSocketData( event:ProgressEvent ):void 
			{
			if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
				trace("[CMD][onSocketData] Socket received " + _instance._ServerSocket.bytesAvailable + " byte(s) of data" );
			
			/* receive data */
			
			parseCmdFromSocket();
		    }
		
		/* the CORE function - to handle Server incoming commands */
		
		private function parseCmdFromSocket():void
			{
			/* read the command from socket */
			
			var Buffer:ByteArray = _instance._ServerSocket.readBytesW();

			Buffer.endian		 = ENDIAN_TYPE;
			
			//trace-debug
//			for (var i:int = 0; i < Buffer.length; i++)
//				trace ("value of byte[" + i + "]: " + Buffer[i]);
				
			/* Parse CMD protocol command */
			
			var CmdResponse:CMDResponse = new CMDResponse();
			
			Buffer.position = 0;

			try 
				{
				/* parse 'envelope' Headers */
								
				CmdResponse.ClientID = uint(Buffer.readInt());

				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][readCmdFromSocket] CliendID received: " + CmdResponse.ClientID);
					
				// Security: test our Client_ID
				if (_ClientID != CmdResponse.ClientID)	
					{
					if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
						trace("[CMD][readCmdFromSocket] Houston, we have a problem - not our identity");
					return;
					}
					
				CmdResponse.SessionID = Buffer.readUTFBytes(40);		// SHA1 length
				CmdResponse.Command  = uint(Buffer.readInt());
				
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][readCmdFromSocket] Command received: " + CmdResponse.Command);
				
				if (CmdResponse.Command == ACTION_SEL_RESP)
					{
					if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
						trace("[CMD][readCmdFromSocket]BINGO!!!!!!!!! - in ACTION_SEL_RESP");
					}
				if ((CmdResponse.Command == DONE_MOVE) || (CmdResponse.Command == ACTION_PAY) || (CmdResponse.Command == ACTION_BUY) || (CmdResponse.Command == ACTION_EXCHANGE) || (CmdResponse.Command == ACTION_PARTNER) || (CmdResponse.Command == ACTION_SELL) || (CmdResponse.Command == ACTION_MORTGAGE) || (CmdResponse.Command == ACTION_UPGRADE) || (CmdResponse.Command == ACTION_AUCTION))
					{
					_instance._currentCMD = ACTION_RES;
					}
				else if ((CmdResponse.Command == ACTION_SEL_RESP) || (CmdResponse.Command == ACTION_BUY_RESP) || (CmdResponse.Command == ACTION_EXC_RESP) || (CmdResponse.Command == ACTION_PAR_RESP) || (CmdResponse.Command == ACTION_AUC_RESP) || (CmdResponse.Command == ACTION_AUC_REQ))
					{
					_instance._currentCMD = ACTION_REQ;
					}
				else
					{
					_instance._currentCMD = CmdResponse.Command;
					}

					/* security test */
			
				if (null == (_instance._commandMap[ _instance._currentCMD ]))
					{
					if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
						trace("[CMD][readCmdFromSocket] out of bounds - no such command in our list");
					return;
					}

				CmdResponse.Size = uint(Buffer.readInt());
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][readCmdFromSocket] Command Size received: " + CmdResponse.Size);
				
				var ContentBA:ByteArray = new ByteArray();

				ContentBA.endian = ENDIAN_TYPE;
				
				var size:uint = CmdResponse.Size;
				if ( size <= Buffer.bytesAvailable)				// Security check - avoid mem leak
					{
					// => Flash Bug that is! => Buffer.readBytes(ContentBA, Buffer.position, CmdResponse.Size);
					// try alternative (works!) until this shit is fixed
					for (var j:uint = 0; j < size; j++)
						ContentBA[j] = Buffer.readByte(); 
					}
				else
					{
					if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
						trace("[CMD][readCmdFromSocket] houston - someone is trying to buffer overflow us...");
					return;
					}
			
				CmdResponse.Content = ContentBA;
		
				/* prevent man-in-the-middle attacks */
				
				var mykey:String = msg_digest(serialize_msg_for_digest(CmdResponse));
				
				if (mykey.localeCompare(CmdResponse.SessionID) != 0)
					{
					if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
						trace("[CMD][readCmdFromSocket] Houston, we have a problem - not a valid hash key!");
					return;
					}
				
				/* Lounch processing function based on the current received CMD */
				
				var processFunc:Function = _instance._commandMap[ _instance._currentCMD ];
			
				processFunc(CmdResponse);
				
				//debug:
				if (CmdResponse.Command == STATUS || CmdResponse.Command == JOIN_RESPONSE)
					{
					var Command:String = (CmdResponse.Command == STATUS) ? "STATUS" : "JOIN_RESPONSE";
					
					if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
						{
						trace("[CMD][readCmdFromSocket] Content received for " + Command + " command:");
						trace("[CMD][readCmdFromSocket] Size: " + size);

						for (var i:int = 0; i < CmdResponse.Content.length; i++)
							trace("	 " + CmdResponse.Content[i]);
	
						trace("[CMD][readCmdFromSocket] End of " + Command + " command received content");
						}
					}
				}
			catch(e:EOFError) 
				{
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
	                trace("[CMD][readCmdFromSocket] " + e);           // EOFError: Error #2030: End of file was encountered.
    	        }

			var cmdResponseEvent:CMDResponseEvent = new CMDResponseEvent(CMDResponseEvent.RESPOND);
			cmdResponseEvent.sCMDResponse(CmdResponse);		// set

			/* dispatching an event will cause GameController to take care of this server input */
			
			dispatchEvent(cmdResponseEvent);
			
			}

		private function writeCmdToSocket(CmdRequest:CMDResponse):void
			{
			try {
				_instance._ServerSocket.flush();
				_instance._ServerSocket.endian = ENDIAN_TYPE;

				_instance._ServerSocket.writeInt(int(CmdRequest.ClientID));	// size = 4 bytes
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][writeCmdToSocket] writing ClientID: " + CmdRequest.ClientID);
				
				_instance._ServerSocket.writeUTFBytes(CmdRequest.SessionID);	// size = 14 bytes (date) + dynamic hash size
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][writeCmdToSocket] writing SessionID: " + CmdRequest.SessionID);
					
				_instance._ServerSocket.writeInt(int(CmdRequest.Command));	// size = 4 bytes
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][writeCmdToSocket] writing CMD: " + CmdRequest.Command);
					
				_instance._ServerSocket.writeInt(int(CmdRequest.Size));		// size = 4 bytes
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][writeCmdToSocket] writing Size: " + CmdRequest.Size);

				/* check type of content if content exists */
				
				if (CmdRequest.Size > 0)
				  switch(CmdRequest.Type)
					{
					case CMDResponse.INT:
						_instance._ServerSocket.writeInt(int(CmdRequest.Content));			// size = 4 bytes
						if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
							trace("[CMD][writeCmdToSocket] writing Content-Type Int: " + int(CmdRequest.Content));
						break;
					case CMDResponse.UINT:
						_instance._ServerSocket.writeInt(int(CmdRequest.Content));	// size = 4 bytes
						if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
							trace("[CMD][writeCmdToSocket] writing Content-Type Int:" + int(CmdRequest.Content));
						break;
					case CMDResponse.AMFOBJECT:
						_instance._ServerSocket.writeObject(Object(CmdRequest.Content));	// size = dynamic
						break;
					case CMDResponse.BYTES:
						_instance._ServerSocket.writeBytes(ByteArray(CmdRequest.Content));				// size = dynamic
						if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
							trace("[CMD][writeCmdToSocket] writing Content-Type ByteArray");
						break;
					default:		// STRING
						_instance._ServerSocket.writeUTFBytes(String(CmdRequest.Content));	// size = dynamic or 16 bytes ?!
						if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
							trace("[CMD][writeCmdToSocket] writing Content-Type String");
						break;
					}
					
	    		_instance._ServerSocket.flush();
				}
			catch (error:IOError)
				{
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][writeCmdToSocket] Error on ServerSocket.writeBytes or flush: " + error);
				}
			}
	

		/* ICMD interface implementation (state functions) */
		
		// cmd code = 100 - Read Challange key from server
		public function readSRVHLO( cmdRes:CMDResponse ):void
			{
			if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
				trace("[CMD][readSRVHLO] GameServer Session ID=" + cmdRes.SessionID);
			
			/* set the next state before responding */
			
			if ( (cmdRes.Command == SRVHLO) && (_instance._currentCMD == SRVHLO) )
				{
				_instance._currentCMD = INTRO;
				cmdRes.Command		  = INTRO;
				}
			else if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
				trace("[CMD][readSRVHLO] Houston, we have a problem with command" + cmdRes.Command + ". should have been " + SRVHLO);
			
			return;
			}
		
		/* cmd code = 120 - client send challange response.
		   server shall return true/false if the command was legal or not - called by the Game Controller */
		
		public function sendINTRO(clientVersion:int, gameID:int):void
			{
			if (_instance._currentCMD != INTRO)
				{
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][sendINTRO] INTRO command was Called in wrong state");
				return;
				}
			
			//trace
			if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
				trace("[CMD][sendINTRO] The command we send is: " + INTRO);

			/* set next state before sending (in this case the state is only interesting for the Controller) */
			
			_instance._currentCMD = ACCEPT;
			
			/* send the request */

			var ba:ByteArray = new ByteArray();
			
			ba.writeInt(clientVersion);
			ba.writeInt(gameID);
			
			sendRequest(INTRO, GLOBALS.INT_SIZE*2, CMDResponse.BYTES, ba);
			}
		
		// cmd code = 130 - server accept client credentials
		public function readACCEPT(cmdRes:CMDResponse):void
			{
			/* move to next machine state */
			
			if ( (cmdRes.Command == ACCEPT) && (_instance._currentCMD == ACCEPT) )
				_instance._currentCMD = PLAYER_TYPE;
			else
				{
				if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
					trace("[CMD][readACCEPT] ACCEPT command was Sent in wrong state");
				return;
				}

			_instance._Authorized = true;
			}
		
		/* most cardinal server command - status/update */
		// cmd code = 180
		public function readSTATUS(cmdRes:CMDResponse):void { }	// moved functionality to GameController !?
		
		/* add chat system */
		
		private function readCHAT(cmdRes:CMDResponse):void
			{
			// managed by game controller
			}
		private function readBROADCAST(cmdRes:CMDResponse):void
			{
			// managed by game controller
			}
		private function readJOINRESP(cmdRes:CMDResponse):void
			{
			// managed by game controller
			}
		private function readACTIONRES(cmdRes:CMDResponse):void
			{
			// managed by game controller
			}
		private function readACTIONREQ(cmdRes:CMDResponse):void
			{
			// managed by game controller
			}
		// cmd code = 160 = send the ready+type request
		public function ready(PlayerType:int):void
			{
			sendRequest(PLAYER_TYPE, GLOBALS.INT_SIZE, CMDResponse.UINT, Object(PlayerType));
			}
			
		public function sendStart():void
			{
				//@IGG: here was GLOBALS.INT_SIZE
			sendRequest(PLAYER_START, 0, CMDResponse.UINT, null);
			}

			
		/* now the game commands */
		
		// send dies throw request - 400
		public function throw_dies():void
			{
			sendRequest(THROW_DIES, 0, CMDResponse.UINT, null);
			}
		// client returns desired move and expect server approval (allow/deny) - 420
		public function done_move(NewPosition:uint):void
			{
			sendRequest(DONE_MOVE, GLOBALS.INT_SIZE, CMDResponse.UINT, Object(NewPosition));
			}
			
		private function onDisconnect(event:Event):void
			{
			if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
				trace("[CMD][onDisconnect] CMD Disconnected. State was: " + _instance._currentCMD + ", event: " + event);
			CMDdisconnect( );
			}
			
		// client disconnect session - 999
		public function CMDdisconnect():void
			{			
			_instance._ServerSocket.close( );
			_instance._ServerSocket.removeEventListener(ProgressEvent.SOCKET_DATA, _instance.onSocketData);
			_instance._ServerSocket.removeEventListener(Event.CLOSE, _instance.CMDdisconnect);
			_instance._ServerSocket.removeEventListener( IOErrorEvent.IO_ERROR, _instance.onConnectError);
			}
			
		/* property actions */
	
		// action none - 430
		public function ac_none():void
			{
			sendRequest(ACTION_NONE, 0, CMDResponse.UINT, null);
			}
		// action pay  - 440 - pay to owner
		public function ac_pay(PlayerID:uint, PropertyID:uint, Sum:uint):void
			{
			var Buffer:ByteArray = new ByteArray( );
			
			Buffer.position = 0;
			Buffer.writeInt(PropertyID);
			Buffer.writeInt(PlayerID);
			Buffer.writeInt(Sum);

			sendRequest(ACTION_PAY, 3*GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}
		// action buy  - 450
		public function ac_buy(PlayerCID:uint, PropertyID:uint, Sum:uint):void
			{
			var Buffer:ByteArray = new ByteArray( );
			
			if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
				trace("[CMD][ac_buy] PlayerID= " + PlayerCID + ", FieldID= " + PropertyID + ", Sum= " + Sum);
				
			Buffer.position = 0;
			Buffer.writeInt(PropertyID);
			Buffer.writeInt(PlayerCID);
			Buffer.writeInt(Sum);

			sendRequest(ACTION_BUY, 3*GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}
		// action sell - 460
		public function ac_sell(PlayerID:uint, PropertyID:uint, Sum:uint):void
			{
			var Buffer:ByteArray = new ByteArray( );

			Buffer.position = 0;
			Buffer.writeInt(PropertyID);
			Buffer.writeInt(PlayerID);
			Buffer.writeInt(Sum);
			if (GLOBALS.DEBUG & GLOBALS.DEBUG_LOGIC)
				trace("[CMD][ac_sell] Field ID = " + PropertyID + ". PlayerID = " + PlayerID + ". Sum = " + Sum);
			
			sendRequest(ACTION_SELL, 3*GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}
		public function ac_mortgage(PlayerID:int, Properties:Array, Sum:int):void
			{
			var Buffer:ByteArray = new ByteArray( );
			
			Buffer.position = 0;
			Buffer.writeInt(Properties.length);
			for (var i:int = 0; i < Properties.length; i++)
				{
				Buffer.writeInt(Properties[i]);
				}
			Buffer.writeInt(PlayerID);
			Buffer.writeInt(Sum);

			sendRequest(ACTION_MORTGAGE, 3*GLOBALS.INT_SIZE + Properties.length*GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}
		public function ac_upgrade(fieldID:int, upgrade_level:int):void
			{
			var Buffer:ByteArray = new ByteArray( );
			
			Buffer.position = 0;
			Buffer.writeInt(fieldID);
			Buffer.writeInt(upgrade_level);
			
			sendRequest(ACTION_UPGRADE, 2*GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}
		public function ac_auction(fieldID:int, sum:int):void
			{
			var Buffer:ByteArray = new ByteArray( );
			
			Buffer.position = 0;
			Buffer.writeInt(fieldID);
			Buffer.writeInt(sum);
			
			sendRequest(ACTION_AUCTION, 2*GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}
		// 555 - Client sends "end turn" to Server once Game Controller fire the command
		public function end_turn():void
			{
			sendRequest(ACTION_ENDTURN, 0, CMDResponse.UINT, null);
			}
		public function ac_resign():void
			{
			sendRequest(RESIGN, 0, CMDResponse.UINT, null);
			}
		
		/* Server Actions Request-Response handlers */
		
		public function ac_req_buy(response:uint):void
			{
			var Buffer:ByteArray = new ByteArray( );

			Buffer.position = 0;
			Buffer.writeInt(response);

			sendRequest(ACTION_BUY_RESP, GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}

		public function ac_req_exchange(response:uint):void
			{
			var Buffer:ByteArray = new ByteArray( );

			Buffer.position = 0;
			Buffer.writeInt(response);

			sendRequest(ACTION_EXC_RESP, GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}

		public function ac_req_partner(response:uint):void
			{
			var Buffer:ByteArray = new ByteArray( );

			Buffer.position = 0;
			Buffer.writeInt(response);

			sendRequest(ACTION_PAR_RESP, GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}

		public function ac_req_sell(response:uint):void
			{
			var Buffer:ByteArray = new ByteArray( );

			Buffer.position = 0;
			Buffer.writeInt(response);

			sendRequest(ACTION_SEL_RESP, GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}

		public function ac_req_auction(fieldID:int,sum:int):void
			{
			var Buffer:ByteArray = new ByteArray( );
			
			Buffer.position = 0;
			Buffer.writeInt(fieldID);
			Buffer.writeInt(sum);
			
			sendRequest(ACTION_AUC_RESP, 2*GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}

			
		/* add chat system */
		
		public function chat(message:String, player_id:uint, rcpt:uint):void
			{
			var Buffer:ByteArray = new ByteArray();
			
			Buffer.writeInt(player_id);
			Buffer.writeInt(rcpt);
//@TP - add support for Base64. replace writeUTFBytes with writeObject:
//			Buffer.writeObject(message);

			Buffer.writeUTFBytes(message);
			sendRequest(CHAT, message.length + 2*GLOBALS.INT_SIZE, CMDResponse.BYTES, Object(Buffer));
			}
		
		/* send Request pattern function */
		
		private function sendRequest(cmd:uint, size:uint, contentType:uint, content:Object):void
			{
			/* fill CMDResponse structure with the cmdType command */

			var CmdRequest:CMDResponse = new CMDResponse();
			
			CmdRequest.ClientID		= _ClientID;
//			CmdRequest.SessionID	= _SessionID;
			CmdRequest.Command		= cmd;
			CmdRequest.Size			= size;
			CmdRequest.Type			= contentType;
			if (size == 0)
				CmdRequest.Content	= null;
			else
				CmdRequest.Content	= Object(content);
				
			CmdRequest.SessionID	= msg_digest(serialize_msg_for_digest(CmdRequest));
						
			/* send to server */
			
			_instance.writeCmdToSocket(CmdRequest);
			}
		
		/* some getters */
		
		public function get SessionID():String { return _instance._SessionID; }
		public function get ServerName():uint { return _instance._ServerName; }
		public function get ServerVersion():uint { return _instance._ServerVersion; }
		
		
		private function serialize_msg_for_digest(cmd:CMDResponse):String
			{
			return (String(cmd.Command)+String(cmd.Size)+String(cmd.Content));
			}
		/* given a date string - compute hash based on that string */
		
		private function msg_digest(msg:String):String
			{
			//var myIP:String = GetUserIP();   - TBD 
			if (null == msg)
				return null;
			
			// we need simple SHA1 Hex hash
			return SHA1.encrypt(_instance._myIP + _instance._SharedSecret + msg);
			}

		private function GetUserIP():String
			{ 
			var js:String="function get_userIP(){return java.net.InetAddress.getLocalHost().getHostAddress();}";
			var userIPInfo:String = null;
			if (ExternalInterface.available)
				userIPInfo = ExternalInterface.call(js).toString(); 
			return userIPInfo; 
			}
		}
}
class SingletonEnforcer {}
